/*
 * Copyright (C) 2010 ST-Ericsson AS
 * Author: Erwan Bracq / erwan.bracq@stericsson.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 */

#define PERF_MEASUREMENT 1

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <getopt.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <time.h>
#include <assert.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <pwd.h>
#include <grp.h>

#include <apr_general.h>
#include <apr_poll.h>
#include <apr_network_io.h>

#include "mid.h"
#include "mid_caif.h"
#include "mid_conf.h"
#include "mid_dev.h"
#include "mid_error.h"
#include "mid_flash.h"
#include "mid_gpio.h"
#include "mid_ipc.h"
#include "mid_log.h"
#include "mid_mfa.h"
#include "mid_power.h"
#include "mid_settings.h"
#include "mid_statemachine.h"
#include "version.h"
#include "atchannel.h"
#include "crash_dump.h"
#include "mid_sys.h"
#include "mid_fw_upgrade.h"

#if defined(MOTOROLA_FEATURE)
#include "private/android_filesystem_config.h"
struct mot_mid mot_mid_info;
#endif

/* Internal mid structure */
static struct mid mid_info;
#ifdef DEBUG_TRACE
struct mid_dbg mid_dbg_info;
#endif
/* Modem firmware */
static union mid_modem_firmware mod_firm;
/* Primary link device */
static struct mid_dev prim_dev;
/* Secondary link device, used only when link swap is specified */
static struct mid_dev sec_dev;

/* Configuration file processing */
static void (*moli_load_cfgfile)(struct mid *mid_info, const char* cfg_file_name);
/* Default settings for device and modem firmware */
static void (*moli_default_settings)(struct mid *mid_info);

static inline void lock_mid_data(void)
{
	apr_status_t status;

	if (unlikely((status = apr_thread_mutex_lock(mid_info.mutex)) != APR_SUCCESS)) {
		char message[100];
		MLGE("MID: Unable to lock internal mid data mutex: %s.",
				apr_strerror(status, message, sizeof(message)));
	}
}

static inline void unlock_mid_data(void)
{
	apr_status_t status;
	if (unlikely((status = apr_thread_mutex_unlock(mid_info.mutex)) != APR_SUCCESS)) {
		char message[100];
		MLGE("MID: Unable to unlock internal mid data mutex: %s.",
				apr_strerror(status, message, sizeof(message)));
	}
}

#define LOCK_MID_DATA	lock_mid_data()
#define UNLOCK_MID_DATA	unlock_mid_data()

int stub_moli_get_crash_dump(struct mid *midinfo, struct mid_dev *dev, const char* dump_directory, const char* dump_directory_header, const char* dump_filename, const char* dump_filename_header, int dump_timestamp, crash_dump_report_t report_type, int max_dump_files)
{
	UNUSED(midinfo);
	UNUSED(dev);
	UNUSED(dump_directory);
	UNUSED(dump_directory_header);
	UNUSED(dump_timestamp);
	UNUSED(dump_filename);
	UNUSED(dump_filename_header);
	UNUSED(report_type);
	UNUSED(max_dump_files);
	MLGD("STUB: Call stub moli_get_crash_dump function -> this function do nothing.");
	return 0;
}

static int stub_moli_configure_security_data(struct mid * mid_info) {
	UNUSED(mid_info);
	MLGD("STUB: Call stub moli_configure_security_data function -> this function do nothing.");
	return 0;
}

static void * stub_moli_boot(struct apr_thread_t* thread, void *arg)
{
	UNUSED(arg);
	UNUSED(thread);
	MLGD("STMACH: MID_LONG_OPS_EVT event set");
	SET_FLAG(mid_info.event, MID_LONG_OPS_EVT);
	mid_info.long_ops_result = MID_FLBOOT_OK;
	MLGD("STUB: Call stub moli_boot function -> this function do nothing.");
	return 0;
}

static void stub_moli_default_settings(struct mid *mid_info)
{
	UNUSED(mid_info);
	MLGD("STUB: Call stub moli_default_settings -> this function do nothing.");
	return;
}

static void stub_moli_load_cfgfile(struct mid *mid_info, const char* cfg_file_name)
{
	UNUSED(mid_info);
	UNUSED(cfg_file_name);
	MLGD("STUB: Call stub moli_load_cfgfile -> this function do nothing.");
}

static int stub_moli_fw_upgrade_flash(void)
{
	return 0;
}
static void stub_moli_fw_upgrade_init(int *error_code_p)
{
	UNUSED(error_code_p);
	return;
}

static int stub_moli_fw_upgrade_status(char **status)
{
	UNUSED(status);
	return 0;
}
static int stub_moli_fw_upgrade_img_xfer(struct mid *mid_info)
{
	UNUSED(mid_info);
	MLGD("STUB: Call stub moli_fw_upgrade_img_xfer -> this function do nothing.");
	return 0;
}

static char * stub_moli_fw_upgrade_get_error_str(struct mid *mid_info, int error_code)
{
	UNUSED(mid_info);
	UNUSED(error_code);
	MLGD("STUB: Call stub moli_fw_upgrade_get_error_str -> this function do nothing.");
	return NULL;
}

static void mid_signal_handler(int signal)
{
	switch (signal) {
	case SIGHUP:
		MLGD("SIG: SIGHUP signal catched. Nothing to do.");
		break;
	case SIGTERM:
		MLGD("SIG: SIGTERM signal catched. MID will shutdown the modem in the next step and exit.");
		/* Clearing Reboot event and start event */
		/* TODO lock - better to replace them by atomic operation if possible */
		mid_info.event &= ~(1<<MID_MODEM_START_EVT);
		mid_info.event &= ~(1<<MID_IPC_RBT_EVT);
		MLGD("STMACH: MID_MODEM_START_EVT event cleared");
		MLGD("STMACH: MID_IPC_RBT_EVT event cleared");
		mid_info.boot_retry_left = 0;
		mid_info.flash_retry_left = 0;
		mid_info.shutdown_retry_left = 0;
		SET_FLAG(mid_info.event, MID_SIG_KILL_EVT);
		MLGD("STMACH: MID_SIG_KILL_EVT event set");
#if defined(MOTOROLA_FEATURE)
		if (mot_mid_info.recovery_mode)
			mid_mfa_stop(mid_info.mfa, false);
#endif
		break;
	}
}

static int daemonize(char *rdir, char *pidfile)
{
	pid_t pid, sid;
#ifndef HAVE_ANDROID_OS
	char pid_str[16];
	int fd;
#else
	UNUSED(pidfile);
#endif
	/* Already a daemon */
	if (getppid() == 1) {
		MLGD("MID: MID try to daemonize. But the process is already executed in background.");

		/* Change the file permissions */
		umask(027);

		/* Change to running directory */
		if (chdir(rdir) < 0) {
			MLGW("MID: Failed to change to running dir (specified in mid.conf)  \"%s\". MID will run on \"/\" folder.",
					rdir);
			if (chdir("/") < 0)
				MLGE("MID: MID will run from current location, as chdir call failed.");
		}

		/* Open PID file */
#ifndef HAVE_ANDROID_OS
		if (pidfile) {
			fd = open(pidfile, O_RDWR|O_CREAT, 0640);
			if (fd < 0) {
				MLGE("MID: MID cannot open PID file: %s. Process PID will not be recorded.", pidfile);
			}
			if (lockf(fd, F_TLOCK, 0) < 0) {
				close(fd);
				MLGE("MID: PID file %s lock failure. Process PID record could be corrupted.", pidfile);
			}
			/* Record pid to file - Failure are ignored. */
			sprintf(pid_str, "%d\n", getpid());
			if (fd >= 0) {
				write(fd, pid_str, strlen(pid_str));
				close(fd);
			}
		}
#endif
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);

		freopen("/dev/null", "r", stdin);
		freopen("/dev/null", "w", stdout);
		freopen("/dev/null", "w", stderr);

		/* Signal handling
		 * Ignore child
		 * signal(SIGCHLD, SIG_IGN);
		 */
		/* Ignore TTY */
		signal(SIGTSTP, SIG_IGN);
		signal(SIGTTOU, SIG_IGN);
		signal(SIGTTIN, SIG_IGN);
		/* Catch hangup */
		signal(SIGHUP, mid_signal_handler);
		/* Catch kill */
		signal(SIGTERM, mid_signal_handler);

		return 0;
	}

	/* Fork process */
	pid = fork();
	if (pid < 0) {
		MLGE("MID: Process fork failure. MID will stop as it is critical.");
		return -1;
	}
	if (pid > 0)
		exit(0);

	/* Change the file permissions */
	umask(027);

	/* Change to running directory */
	if (chdir(rdir) < 0) {
		MLGW("MID: Failed to change to running dir (specified in mid.conf)  \"%s\". MID will run on \"/\" folder.",
				rdir);
		if (chdir("/") < 0)
			MLGE("MID: MID will run from current location, as chdir call failed.");
	}

	/* Open PID file */
#ifndef HAVE_ANDROID_OS
	if (pidfile) {
		fd = open(pidfile, O_RDWR|O_CREAT, 0640);
		if (fd < 0) {
			MLGE("MID: MID cannot open PID file: %s. Process PID will not be recorded.", pidfile);
		}
		if (lockf(fd, F_TLOCK, 0) < 0) {
			close(fd);
			MLGE("MID: PID file %s lock failure. Process PID record could be corrupted.", pidfile);
		}
		/* Record pid to file - Failure are ignored. */
		sprintf(pid_str, "%d\n", getpid());
		if (fd >= 0) {
			write(fd, pid_str, strlen(pid_str));
			close(fd);
		}
	}
#endif

	/* Create a new SID for the child process */
	sid = setsid();
	if (sid < 0) {
		MLGE("MID: Unable to get SID for child process. MID will stop as it is critical.");
		return -1;
	}

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	freopen("/dev/null", "r", stdin);
	freopen("/dev/null", "w", stdout);
	freopen("/dev/null", "w", stderr);

	/* Signal handling
	 * Ignore child
	 * signal(SIGCHLD, SIG_IGN);
	 */
	/* Ignore TTY */
	signal(SIGTSTP, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	/* Catch hangup */
	signal(SIGHUP, mid_signal_handler);
	/* Catch kill */
	signal(SIGTERM, mid_signal_handler);

	return 0;
}

static bool get_user_id_by_user_name(const char* user_name, uid_t* uid)
{
	struct passwd *user;
	if (user_name == NULL) {
		MLGE("No user name supplied");
		return false;
	}
	user = getpwnam(user_name);
	if (!user) {
		MLGE("Failed to find user \"%s\"", user_name);
		return false;
}
	*uid = user->pw_uid;
	return true;
}

static bool get_group_id_by_group_name(const char* group_name, gid_t* gid)
{
	struct group *group;
	if (group_name == NULL) {
		MLGE("No group name supplied");
		return false;
	}
	group = getgrnam(group_name);
	if (!group) {
		MLGE("Failed to find group \"%s\"", group_name);
		return false;
	}
	*gid = group->gr_gid;
	return true;
}

static bool switch_to_user_with_capabilities(const char* run_as_user, const char* run_as_group, const char* capabilities)
{
	uid_t user_id;
	gid_t group_id;
	cap_t caps_to_set = NULL;
	cap_t end_caps = NULL;
	bool success = false;
#if defined(MOTOROLA_FEATURE)
    gid_t gid =  AID_BLUETOOTH;
#endif

	MLGD("Switching to user \"%s\", group \"%s\" with capabilities \"%s\"", run_as_user, run_as_group, capabilities);

	if (!get_user_id_by_user_name(run_as_user, &user_id))
		goto exit;
	if (!get_group_id_by_group_name(run_as_group, &group_id))
		goto exit;

	caps_to_set = cap_from_text(capabilities);
	if (caps_to_set == NULL) {
		MLGE("Invalid capabilities string: \"%s\"", capabilities);
		goto exit;
	}

	if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
		MLGE("Failed to set PR_SET_KEEPCAPS flag: %s", strerror(errno));
		goto exit;
	}

#if defined(MOTOROLA_FEATURE)
    // Set Bluetooth Group as this is needed to re-connect to DBUS
    setgroups(1, &gid);
#endif

	if (setregid(group_id, group_id) == -1) {
		MLGE("Failed to set group ID of self: %s", strerror(errno));
		goto exit;
	}
	if (setreuid(user_id, user_id) == -1) {
		MLGE("Failed to set user ID of self: %s", strerror(errno));
		goto exit;
	}

	// Now set the caps again, since the effective set was cleared
	MLGD("Setting capabilities to: %s", cap_to_text(caps_to_set, 0));
	if(cap_set_proc(caps_to_set) == -1) {
		MLGE("Failed to set capabilities of self after setuid: %s", strerror(errno));
		goto exit;
	}

	end_caps = cap_get_proc();
	MLGD("End: user=%d, capabilities=\"%s\"", getuid(), cap_to_text(end_caps, 0));

	success = true;

exit:
	if (caps_to_set) cap_free(caps_to_set);
	if (end_caps) cap_free(end_caps);
	return success;
}

static void mid_usage(void)
{
	printf(
		"Usage: ./mid [options]\n"
		"\n"
		" -h, --help           Display this usage information.\n"
		" --cfg CFG_FILE_PATH  Load configuration from file. default "
			MID_CFG_FILE "\n"
		"\n"
	);
}

#if defined(MOTOROLA_FEATURE)
static void* mode_checker(void *arg)
{
	MID_State_t state = STATE_OFF;
	struct mid *mid_info_p = (struct mid *)arg;

	MLGD("mode thread: starting");
	while(mid_info_p) {
		sleep(1);
		if (state == mid_info_p->state)
			continue;
		state = STATE_OFF;
		switch(mid_info_p->state) {
		case STATE_BOOT:
			MLGD("mode thread: testing for ITP mode");
			if (!my_system(&mid_info, mot_mid_info.itp_mode_cmd)) {
				MLGI("mode thread: ITP mode detected");
				state = STATE_BOOT;
				mot_mid_info.dual_boot_mode = 1;
			}
			break;
		case STATE_ON:
			MLGD("mode thread: testing for signaling mode");
			if (!my_system(&mid_info, mot_mid_info.norm_mode_cmd)) {
				MLGI("mode thread: signaling mode detected");
				state = STATE_ON;
			}
			break;
		default:
			break;
		}
	}
	MLGD("mode thread: exiting");
	pthread_exit(NULL);
	return(NULL);
}
#endif

#define NEXT_STATE(_state_) if (_state_!= MID_SAME_STATE) Self_p->state=_state_

int main(int argc, char *argv[])
{
	size_t i = 0;
	int ipc_event = 0;
	time_t time_entered_state;
	/* Configuration file */
#ifdef MID_PARSE_KER_CMDLINE
	char *kernel_cmd_line;
#define MAX_STR_SIZE 65534
	char garbage[MAX_STR_SIZE + 1];
	int cmd_line_sz = 0;
	int read_line_sz;
	int fproc;
	char* cfg_filename = NULL;
	char* pindex = NULL;
#endif
	/* Return value */
	int res = 0;
	/* Dynamic library handle */
	char *dl_error = NULL;
	char *dl_name = NULL;
	/* Command line parameters */
	int option_index = 0;
	int cmd_args;
	/* Indicates if a kernel configuration parameter is available */
	int kernel_cfg = 0;
	enum {
		CFG_FILE = 1
#if defined(MOTOROLA_FEATURE)
		, FIRM_DIR = 2
#endif
	};
	struct option long_options[] = {
		{ "help", no_argument, NULL, 'h' },
		{ "cfg", required_argument, NULL, CFG_FILE },
#if defined(MOTOROLA_FEATURE)
		{ "firm_dir", required_argument, NULL, FIRM_DIR },
#endif
		{ 0, 0, 0, 0 }
	};
	char* cfgfile;
	struct mid_config* config;
	apr_status_t apr_status;
#if defined(MOTOROLA_FEATURE)
    pthread_t mode_checker_td;
   /* Indicates that we are reconnecting to DBUS */
   int reconnect =0;
#endif

	if ((apr_status = apr_app_initialize(&argc, (char const* const**)&argv, NULL)) != APR_SUCCESS) {
		char message[100];
		MLGE("MID: Failed to initialize Apache Portable Runtime: %s. This critical MID will exit now.\n",
			apr_strerror(apr_status, message, sizeof(message)));
		exit(EXIT_FAILURE);
	}
	opterr = 0;
	optind = 0;

	memset(&mid_info, 0, sizeof(struct mid));
	memset(&mod_firm, 0, sizeof(union mid_modem_firmware));
	memset(&prim_dev, 0, sizeof(struct mid_dev));
	memset(&sec_dev, 0, sizeof(struct mid_dev));
	config = calloc(1, sizeof(struct mid_config));

	moli_load_cfgfile = &stub_moli_load_cfgfile;
	moli_default_settings = &stub_moli_default_settings;
	mid_info.moli_configure_security_data = &stub_moli_configure_security_data;
	mid_info.moli_boot = &stub_moli_boot;
	mid_info.moli_get_crash_dump = &stub_moli_get_crash_dump;
	mid_info.moli_fw_upgrade_get_error_str = &stub_moli_fw_upgrade_get_error_str;
	mid_info.moli_fw_upgrade_flash = &stub_moli_fw_upgrade_flash;
	mid_info.moli_fw_upgrade_init = &stub_moli_fw_upgrade_init;
	mid_info.moli_fw_upgrade_status = &stub_moli_fw_upgrade_status;
	mid_info.moli_fw_upgrade_img_xfer = &stub_moli_fw_upgrade_img_xfer;
	mid_info.config = config;

#ifdef DEBUG_TRACE
	mid_dbg_info.log_fd = NULL;
#endif
	mid_info.pid_fd = NULL;
	mid_info.cfg_fd = NULL;

	mid_info.power_on_release = 0;
#if defined(MOTOROLA_FEATURE)
	mot_mid_info.recovery_mode = 0;
#endif

	mid_info.pool=NULL;
	if((apr_status = apr_pool_create(&mid_info.pool, NULL)) != APR_SUCCESS) {
		char message[100];
		MLGE("MID: Failed to create memory pool: %s. MID will exit now.\n",
			apr_strerror(apr_status, message, sizeof(message)));
		goto error;
	}

	/* Settings order 1) default (include files) 2) configuration file
	 * 3) command line arguments 4) kernel command line */
	/* Default settings initialization */
	/* Daemon working dir - Firmware dir */
	/* Modem IDs */
	mid_info.modem_build_string = MODEM_BUILD_STRING;
	config->sec_data_file = NULL;
#ifdef DEBUG_TRACE
	mid_dbg_info.mid_dbg_level = MID_DBG_DEFAULT_LEVEL;
	mid_dbg_info.logfile = MID_LOG_FILE;
#endif
	cfgfile = MID_CFG_FILE;

	/* Set up default path for initial and optional secondary link */
	prim_dev.name = PRI_DEFAULT_PATH;
	sec_dev.name = SEC_DEFAULT_PATH;
	/* Default values for modem firmware, and link defined
	 * in specific moli implematation
	 */
	config->prim_dev_config = &prim_dev;
	config->sec_dev_config = &sec_dev;
	config->mod_firm_config = &mod_firm;

	MLGD("MID: ST-Ericsson Modem Init Daemon started: %s.", VERSION);

	mid_info.at_chnl_fd = -1;

	if (unlikely((apr_status = apr_thread_mutex_create(&mid_info.mutex, 0, mid_info.pool)) != APR_SUCCESS)) {
		char message[100];
		MLGE("MID: Unable to init internal mid data mutex: %s. Problems are forecasted when mutex will be used.\n",
			apr_strerror(apr_status, message, sizeof(message)));
	}
	mid_info.dl_handle = NULL;

#ifdef PERF_MEASUREMENT
	if (clock_gettime(CLOCK_MONOTONIC, &mid_info.midStarted))
		MLGW("TIME: Error getting monotonic clock. Following time report should be considered as wrong.");

	float used_time;
	used_time = (float)(mid_info.midStarted.tv_sec+(float)(mid_info.midStarted.tv_nsec/1000000000));
	MLGD("TIME: Started (MONOTONIC CLOCK - clock origin is unspecified) at: %4.2f sec.",used_time);

#endif
#ifdef MID_PARSE_KER_CMDLINE
	/* Read /proc/cmdline */
	fproc = open("/proc/cmdline", O_RDONLY);
	if (fproc == -1) {
		kernel_cmd_line = NULL;
		MLGD("MID: No kernel command line accessible from /proc/cmdline.");
	}
	else {
		/* Count kernel cmd line size */
		while ((read_line_sz = read(fproc, garbage, MAX_STR_SIZE)) != 0) {
			if (read_line_sz == -1) {
				MLGE("MID: Unable to read kernel command line: %s.", strerror(errno));
				kernel_cmd_line = NULL;
				goto ker_cmdline_proc_close;
			}
			cmd_line_sz += read_line_sz;
		}
		/* Reset cmd line pointer */
		if (lseek(fproc, 0, SEEK_SET) == -1) {
			MLGE("MID: Unable to lseek position in /proc/cmdline.");
			kernel_cmd_line = NULL;
			goto ker_cmdline_proc_close;
		}
		/* Empty cmd line ? */
		if (cmd_line_sz == 0) {
			MLGE("MID: /proc/cmdline string is empty.");
			kernel_cmd_line = NULL;
			goto ker_cmdline_proc_close;
		}
		/* Add room for end of line while allocating */
		kernel_cmd_line = malloc((cmd_line_sz + 1) * sizeof(char));
		if (!kernel_cmd_line) {
			MLGE("MID: Unable to malloc cmd_line string.");
			kernel_cmd_line = NULL;
			goto ker_cmdline_proc_close;
		}
		if (read(fproc, kernel_cmd_line, cmd_line_sz) == -1) {
			MLGE("MID: Unable to read /proc/cmdline.");
			free(kernel_cmd_line);
			kernel_cmd_line = NULL;
			goto ker_cmdline_proc_close;
		}

		/* Find LF and trucate the string */
		pindex = index(kernel_cmd_line, 0x0A);
		if (pindex != NULL)
			*pindex = '\0';
		kernel_cmd_line[cmd_line_sz] = '\0';
		MLGD("MID: Success to read /proc/cmdline, get: %s.", kernel_cmd_line);
	}

ker_cmdline_proc_close:
	close(fproc);

	/* Parse /proc/cmdline, check kernel command line parameters for a configuration file */
	if ((kernel_cmd_line != NULL) &&
			((cfg_filename = strstr(kernel_cmd_line, CMD_PARM_NAME)) != NULL)) {
		/* point on '=' character */
		cfg_filename = index(cfg_filename, 0x3d);
		if (cfg_filename == NULL) {
			MLGE("MID: Kernel command line parse error.");
			free(kernel_cmd_line);
			goto ker_cmdline_fallback;
		}
		cfg_filename++;
		/* point on first white space */
		pindex = index(cfg_filename, 0x20);
		if (pindex != NULL)
			*pindex = '\0';
		/* If no white space, end of string is already marked */
		cfgfile=strdup(cfg_filename);

		MLGD("MID: Found a configuration file name on kernel command line parameters: %s.", cfgfile);
		kernel_cfg = 1;
		free(kernel_cmd_line);
	} else {
ker_cmdline_fallback:
		MLGD("MID: No %s option on kernel command line. Try to check for mid --cfg option.",  CMD_PARM_NAME);
		kernel_cfg = 0;
	}
#endif

	/* Parse command line */
	while ((cmd_args = getopt_long_only(argc, argv, "h", long_options,
			&option_index)) != -1) {
		switch (cmd_args) {
		case 'h':
			mid_usage();
			exit(EXIT_SUCCESS);
		case CFG_FILE:
			if (!kernel_cfg)
				cfgfile = strdup(optarg);
			break;
#if defined(MOTOROLA_FEATURE)
		case FIRM_DIR:
			/* Processed later */
			break;
#endif
		default:
			MLGE("MID: Invalid option / parameter: %s. Will exit now.", argv[optind]);
			mid_usage();
			exit(EXIT_FAILURE);
		}
	}

	if (argc != optind) {
		int i;
		MLGE("MID: Too much parameter on command line: ");
		for (i = optind; i < argc; i++)
			MLGE("%s", argv[i]);
		MLGE("MID: Will exit now due command line parameters error.");
		mid_usage();
		exit(EXIT_FAILURE);
	}

	if (!mid_conf_load(config, cfgfile)) {
		goto error;
	}

	/* Shared library loading */
	if (strcmp(config->modem_arch, "OSMIUM") == 0) {
			prim_dev.flb_alloc_usb_unit = NULL;
			sec_dev.flb_alloc_usb_unit = NULL;
			dl_name = MID_MOLIU33x_NAME;
	}
	else if (strcmp(config->modem_arch, "HASSIUM") == 0)
		dl_name = MID_MOLIU5xx_NAME;
	else if (strcmp(config->modem_arch, "THORIUM") == 0) {
		prim_dev.flb_alloc_usb_unit = NULL;
		sec_dev.flb_alloc_usb_unit = NULL;
		dl_name = MID_MOLIU5xx_NAME;
	}
	if (dl_name != NULL) {
		MLGD("LIB: Loading %s library:", dl_name);
		mid_info.dl_handle = dlopen(dl_name, RTLD_LAZY);
		if (!mid_info.dl_handle) {
			dl_error = (char *)dlerror();
			MLGE("LIB: error: %s. MID will exit now.", dl_error);
			goto error;
		}
		moli_load_cfgfile = dlsym(mid_info.dl_handle, "moli_load_cfgfile");
		if ((dl_error = (char *)dlerror()) != NULL)
			goto error;
		MLGD("LIB: moli_check_cfgfile symbol loaded.");
		moli_default_settings = dlsym(mid_info.dl_handle, "moli_default_settings");
		if ((dl_error = (char *)dlerror()) != NULL)
			goto error;
		MLGD("LIB: moli_default_settings symbol loaded.");
		if (config->initial_enable_bootldr) {
			mid_info.moli_configure_security_data = dlsym(mid_info.dl_handle, "moli_configure_security_data");
			if ((dl_error = (char *)dlerror()) != NULL)
				goto error;
			MLGD("LIB: moli_configure_security_data symbol loaded.");
			mid_info.moli_boot = dlsym(mid_info.dl_handle, "moli_boot");
			if ((dl_error = (char *)dlerror()) != NULL)
				goto error;
			MLGD("LIB: moli_boot symbol loaded.");
		}
		if (strncmp(dl_name, MID_MOLIU5xx_NAME, strlen(MID_MOLIU5xx_NAME)) == 0) {
			if (config->initial_enable_bootldr) {
				prim_dev.flb_alloc_usb_unit = dlsym(mid_info.dl_handle, "flb_alloc_usb_unit");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: flb_alloc_usb_unit symbol loaded.");
				sec_dev.flb_alloc_usb_unit = dlsym(mid_info.dl_handle, "flb_alloc_usb_unit");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: flb_alloc_usb_unit symbol loaded.");
			} else {
				mid_info.moli_fw_upgrade_get_error_str = dlsym(mid_info.dl_handle, "moli_fw_upgrade_get_error_str");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: moli_fw_upgrade_get_error_str symbol loaded.");

				mid_info.moli_fw_upgrade_flash = dlsym(mid_info.dl_handle,"moli_fw_upgrade_flash");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: moli_fw_upgrade_flash symbol loaded.");

				mid_info.moli_fw_upgrade_init = dlsym(mid_info.dl_handle,"moli_fw_upgrade_init");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: moli_fw_upgrade_init symbol loaded.");

				mid_info.moli_fw_upgrade_status = dlsym(mid_info.dl_handle,"moli_fw_upgrade_status");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: moli_fw_upgrade_status symbol loaded.");

				mid_info.moli_fw_upgrade_img_xfer = dlsym(mid_info.dl_handle,"moli_fw_upgrade_img_xfer");
				if ((dl_error = (char *)dlerror()) != NULL)
					goto error;
				MLGD("LIB: moli_fw_upgrade_img_xfer symbol loaded.");
			}
			mid_info.moli_get_crash_dump = dlsym(mid_info.dl_handle, "moli_get_crash_dump");
			if ((dl_error = (char *)dlerror()) != NULL)
				goto error;
			MLGD("LIB: moli_get_crash_dump symbol loaded.");
		}
		MLGD("LIB: %s library loaded successfully.", dl_name);
	}
	moli_default_settings(&mid_info);
	moli_load_cfgfile(&mid_info, cfgfile);
#if defined(MOTOROLA_FEATURE)
	/* Parse command line for --firm_dir, which overrides configuration file value for initial_rdir */
	optind = 0;
	while ((cmd_args = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) {
		switch (cmd_args) {
		case FIRM_DIR:
			config->initial_rdir = malloc((strlen(optarg) + 1) * sizeof(char));
			if (config->initial_rdir == NULL) {
				MLGE("Unable to allocate internal mid initial_rdir: %s. Will exit now.",  strerror(errno));
				if (mid_info.dl_handle != NULL)
					dlclose(mid_info.dl_handle);
				exit(EXIT_FAILURE);
			}
			strncpy(config->initial_rdir, optarg, strlen(optarg));
			config->initial_rdir[strlen(optarg)] = '\0';
			/* --firm_dir option is only used in recovery mode for Motorola devices. */
			/* Use this as an indication to handle specific needs in recovery mode. */
			MLGE("Using config->initial_rdir: '%s' for recovery mode", config->initial_rdir);
			mot_mid_info.recovery_mode = 1;
			config->mod_prefl_cmd = mot_mid_info.recovery_mod_prefl_cmd;
			break;
		}
	}

	if (argc != optind) {
		int i;
		MLGE("Invalid parameter: ");
		for (i = optind; i < argc; i++)
			MLGE("%s. Will exit now.", argv[i]);
		mid_usage();
		if (mid_info.dl_handle != NULL)
			dlclose(mid_info.dl_handle);
		exit(EXIT_FAILURE);
	}
#endif /* MOTOROLA_FEATURE */

	mid_info.mfa = mid_mfa_init(&mid_info, mid_info.config, mid_info.pool);

	/* One-shot configuration */
	(void)my_system(&mid_info, mid_info.config->mod_init_cmd);

	if (config->daemonize) {
		if (daemonize(config->initial_rdir, config->pidfile)) {
			MLGE("MID: Daemonization error. Will exit now.");
			if (mid_info.dl_handle != NULL)
				dlclose(mid_info.dl_handle);
			exit(EXIT_FAILURE);
		}
	} else {
		/* Change to running directory */
		if (chdir(config->initial_rdir) < 0) {
			MLGW("MID: Failed to change to running dir \"%s\". MID will run on \"/\" folder.",
					config->initial_rdir);
			if (chdir("/") < 0)
				MLGE("MID: MID will run from current location.");
		}
		/* Catch hangup */
		signal(SIGHUP, mid_signal_handler);
		/* Catch kill */
		signal(SIGTERM, mid_signal_handler);
	}

#ifndef HAVE_ANDROID_OS
#ifdef DEBUG_TRACE
	if (mid_dbg_info.logfile && mid_dbg_info.log_to_file) {
		mid_dbg_info.log_fd = fopen(mid_dbg_info.logfile, "w");
		if (mid_dbg_info.log_fd == NULL) {
			MLGE("MID: Log file open failure: %s, %s. Will exit now.", mid_dbg_info.logfile, strerror(errno));
			if (mid_info.dl_handle != NULL)
				dlclose(mid_info.dl_handle);
			exit(EXIT_FAILURE);
		}
	}
#endif
#endif


	mid_info.mid_power = mid_power_init();
	if (mid_info.mid_power == NULL) {
		MLGE("Failed to initialize power subsystem");
		goto error;
	}

	MLGD("MID: Modem_ARCH:%d %s | Modem_Build:(pending...)", mid_info.config->modem_arch_id, mid_info.config->modem_arch);

	/* Remove end "/" in directory name (if any) */
	cleanupdirname(&mid_info);

	/* Ensure service gpio is released */
	mid_enable_gpio_service_safe_mode(&mid_info);

	/* Request GPIOs */
	mid_info.gpios = mid_gpio_init(mid_info.config->mid_gpios, mid_info.config->gpio_base_path, mid_info.config->gpio_ctrl, mid_info.config->gpio_no_aclow, mid_info.config->gpio_no_xport, mid_info.config->gpio_no_dir);
	if (mid_info.gpios == NULL) {
		MLGE("GPIO: Unable to init GPIOs. This is critical, MID will exit now.");
		state_all_evt_sig_kill(&mid_info);
		if (mid_info.dl_handle != NULL)
			dlclose(mid_info.dl_handle);
		goto error;
	}

	/* Connect IPC */
	res = -EMID;
	MLGD("DBUS: Connecting to DBUS... (looping to connect)");
	while (res != 0) {
#if defined(MOTOROLA_FEATURE)
		res = ipc_medium_connect(&mid_info,reconnect);
#else
        res = ipc_medium_connect(&mid_info);
#endif
		if (res != 0)
			usleep(MID_IPC_OPS_DELAY);
	};
	MLGD("DBUS: Connected to DBUS.");

#if defined(MOTOROLA_FEATURE)
	if (mot_mid_info.recovery_mode == 0) {
#endif
	// Now that all modules got their chance to initialize, we can drop
	// all privileges not needed during normal operations.
	if (!switch_to_user_with_capabilities("system", "radio", "CAP_SYS_MODULE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_BOOT=ep")) {
		MLGE("Failed to drop privileges");
		goto error;
	}
#if defined(MOTOROLA_FEATURE)
        }
#endif
#if defined(MOTOROLA_FEATURE)
	if (strncmp("service", mid_info.config->runtime_mode, strlen("service")) != 0) {
#endif
	/* Initial startup trig a modem start event */
	MLGD("STMACH: MID_MODEM_START_EVT event set");
	SET_FLAG(mid_info.event, MID_MODEM_START_EVT);
#if defined(MOTOROLA_FEATURE)
        }
#endif

	mid_disable_gpio_out_safe_mode(&mid_info);

	/* Ensure modem is off */
	/* TODO check if an AT channel can be openned to shutdown */
	shutdown_modem(&mid_info);

	/* Set all ios in safe mode */
	mid_enable_io_safe_mode(&mid_info);

	/* Initial state */
	mid_info.state = STATE_OFF;
	time_entered_state = time(NULL);

	mid_info.flash_retry_left = mid_info.config->flash_retry;
	mid_info.boot_retry_left = mid_info.config->boot_retry;
	mid_info.shutdown_retry_left = mid_info.config->shutdown_retry;
	mid_info.boot_failed = 0;
	mid_info.flash_failed = 0;
	mid_info.modem_warm_start = 0;

	mid_info.moli_fw_upgrade_init(&mid_info.fw_upgr_last_error);

#if defined(MOTOROLA_FEATURE)
	res = pthread_create(&mode_checker_td, NULL, &mode_checker, (void*)&mid_info);
#endif
	/* Poll filedescriptor */
	for (;;) {
		const size_t guessed_num_fds = ipc_get_num_fds() + 3;
		apr_pollfd_t fds[guessed_num_fds];
		size_t num_fds;
		size_t num_ipc_fds;
		apr_file_t* rst2_fd;
		apr_file_t* pwrrstin_fd;
		bool has_mfa_fd;
		apr_status_t status;
		apr_int32_t num_signalled;
		char message[100];

		LOCK_MID_DATA;

		num_fds = num_ipc_fds = ipc_get_fds(fds, guessed_num_fds - 3);

		rst2_fd = mid_gpio_get_fd(mid_info.gpios, GPIO_ID_RESOUT2);
		pwrrstin_fd = mid_gpio_get_fd(mid_info.gpios, GPIO_ID_PWRRSTIN);
        
		if (rst2_fd != NULL) {
			fds[num_fds].desc_type = APR_POLL_FILE;
			fds[num_fds].desc.f = rst2_fd;
			fds[num_fds].reqevents = APR_POLLPRI;
			fds[num_fds].rtnevents = 0;
			num_fds++;
		}

		if (pwrrstin_fd != NULL) {
			fds[num_fds].desc_type = APR_POLL_FILE;
			fds[num_fds].desc.f = pwrrstin_fd;
			fds[num_fds].reqevents = APR_POLLPRI;
			fds[num_fds].rtnevents = 0;
			num_fds++;
		}

		if ((has_mfa_fd = mid_mfa_get_fd(mid_info.mfa, &fds[num_fds]))) {
			num_fds++;
		}

		UNLOCK_MID_DATA;

		/* If we're in a none transient state we allow sleep */
		if (!mid_statemachine_get_current_timeout(&mid_info))
			if (!mid_permit_suspend(mid_info.mid_power))
				MLGE("POW: Unable to allow system to suspend. MID will continue anyway but system power management may be disturbed.");

		/* Poll */
		status = apr_poll(fds, num_fds, &num_signalled, MID_POLL_LAT * 1000000);

		switch(status) {
		case APR_EINTR:
			MLGI("MID: Received interrupt condition while polling: %s. MID will process events that are set.", apr_strerror(status, message, sizeof(message)));
			break;
		case APR_TIMEUP:
			/* Here we check if we have a timeout on the POLL, that we don't have */
			/* any pending event, we are in a transient state and that we exceeded */
			/* time in the current state. This indicates that we are in some deadlock */
			/* and that we will try to recover. */
			if ((time(NULL) > time_entered_state + mid_statemachine_get_current_timeout(&mid_info)) &&
			    mid_statemachine_get_current_timeout(&mid_info) > 0) {
				MLGE("MID: MID in state (%s) and maximum time in this state is exceeded.", mid_statemachine_get_name_of_current_state(&mid_info));
				LOCK_MID_DATA;
				MLGE("STMACH: MID_TIMEOUT_EVT event set.");
				SET_FLAG(mid_info.event, MID_TIMEOUT_EVT);
				UNLOCK_MID_DATA;
			}
			break;
		case APR_SUCCESS:
			MLGD("MID: Received event(s) on monitored file descriptor(s). MID will check event(s) source(s)");
			for (i = 0; i < num_ipc_fds; i++) {
				if (fds[i].rtnevents) {
#if defined(MOTOROLA_FEATURE)
                    if (fds[i].rtnevents & APR_POLLHUP)
                    {
                        MLGD("MID: DBUS HANG UP event received");
                        // Clean up and Re-connect to DBUS
                        ipc_medium_cleanup();
                        // Wait for a second and then re-connect to DBUS
                        usleep(MID_IPC_OPS_DELAY);
                        usleep(MID_IPC_OPS_DELAY);
                        /* Re-Connect IPC */
                        reconnect = 1;
	                    res = -EMID;
                        MLGD("DBUS: Re-Connecting to DBUS... (looping to connect)");
	                    while (res != 0) {
		                    res = ipc_medium_connect(&mid_info,reconnect);
		                if (res != 0)
			                usleep(MID_IPC_OPS_DELAY);
                        }
                        ipc_event = 0;
                    }
                    else
#endif
                    {
					    MLGD("MID: DBUS activity received.");
					    ipc_notify_for_event(i, fds[i].rtnevents);
					    ipc_event = 1;
                    }
				}
			}
			if (ipc_event) {
				if (!mid_prevent_suspend(mid_info.mid_power))
					MLGE("POW: Unable to prevent system to suspend. MID will continue anyway but power management may disturbe MID operation.");
				ipc_process_event();
				ipc_event = 0;
			}

			if (rst2_fd != NULL) {
				if (fds[i].rtnevents & APR_POLLPRI) {
					int status;
					MLGD("MID: GPIO RESOUT2 activity received.");
					status = mid_gpio_get_value(mid_info.gpios, GPIO_ID_RESOUT2);
					LOCK_MID_DATA;
					MLGD("STMACH: MID_GPIO_RST2_EVT event set.");
					SET_FLAG(mid_info.event, MID_GPIO_RST2_EVT);
					UNLOCK_MID_DATA;
					MLGD("GPIO: New GPIO RESOUT2 logical level (1 active, 0 inactive) %d.", status);
				}
				i++;
			}
			if (pwrrstin_fd != NULL) {
				if (fds[i].rtnevents & APR_POLLPRI) {
					int status;
					MLGD("MID: GPIO PWRRSTIN activity received.");
					status = mid_gpio_get_value(mid_info.gpios, GPIO_ID_PWRRSTIN);
					LOCK_MID_DATA;
					MLGD("STMACH: MID_GPIO_PRST_EVT event set.");
					SET_FLAG(mid_info.event, MID_GPIO_PRST_EVT);
					UNLOCK_MID_DATA;
					MLGD("GPIO: New GPIO PWRRSTIN logical level (1 active, 0 inactive) %d.", status);
				}
				i++;
			}
			if (has_mfa_fd) {
				mid_mfa_receive_message(&fds[i], &mid_info, mid_info.mfa);
				i++;
			}

			break;
		default:
			MLGE("MID: Poll listening problem: %s, This error is ignored and MID will continue anyway. Unexpected various error could occur from now.", apr_strerror(status, message, sizeof(message)));
			break;
		}
		/* Process all events */
		mid_statemachine_process_all_events(&mid_info, &time_entered_state);
	}
	if (mid_info.dl_handle != NULL)
		dlclose(mid_info.dl_handle);
	exit(EXIT_SUCCESS);
error:
	MLGE("MID: MID will exit due to earlier error");
	if (mid_info.dl_handle != NULL)
		dlclose(mid_info.dl_handle);
	exit(EXIT_FAILURE);
}
